[assembly: System.Web.UI.WebResource("BM.Tools.WebControls.MForm.Controls.Additions.Restriction.js", "application/x-javascript")]
[assembly: System.Web.UI.WebResource("BM.Tools.WebControls.MForm.Controls.Additions.Restriction.debug.js", "application/x-javascript")]
[assembly: System.Web.UI.ScriptResource("BM.Tools.WebControls.MForm.Controls.Additions.Restriction.js", "BM.Tools.WebControls.MForm.Resources.RestrictionErrorMessageResource", "BM.RestrictionErrors")]
[assembly: System.Web.UI.WebResource("BM.Tools.WebControls.MForm.Controls.Additions.RestrictionTypes.js", "application/x-javascript")]
[assembly: System.Web.UI.ScriptResource("BM.Tools.WebControls.MForm.Controls.Additions.RestrictionTypes.js", "BM.Tools.WebControls.MForm.Resources.RestrictionTypeResource", "BM.RestrictionTypes")]

namespace BM.Tools.WebControls.MForm.Controls.Additions
{
    using System;
    using System.Globalization;
    using System.Text.RegularExpressions;
    
    using AjaxControlToolkit;
    
    using BM.Tools.WebControls.MForm.Controls.Helper;
    using BM.Tools.WebControls.MForm.Controls.Items;

    using ValueHandlers;

    /// <summary>
    /// Restriction is a type of <see cref="Constraint" /> that reflects Xml Schema simple type restrictions.
    /// These restrictions are checked both on client and server side.
    /// Restrictions can limit the length of a field, a numeric value of the field, can match against a regular expression pattern, etc.
    /// </summary>
    [ClientScriptResource("BM.Tools.WebControls.MForm", "BM.Tools.WebControls.MForm.Controls.Additions.Restriction.js")]
    [ClientScriptResource("BM.Tools.WebControls.MForm", "BM.Tools.WebControls.MForm.Controls.Additions.RestrictionTypes.js")]
    public class Restriction : Constraint
    {        
        private static Regex numbersRegex = new Regex(@"\d");

        /// <summary>
        /// Initializes a new instance of the <see cref="Restriction"/> class. 
        /// </summary>
        public Restriction()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Restriction"/> class. 
        /// </summary>
        /// <param name="type">
        /// The restriction type.
        /// </param>
        /// <param name="value">
        /// The value of the restriction type.
        /// </param>
        public Restriction(RestrictionType type, string value)
        {
            RestrictionType = type;
            Value = value;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Restriction"/> class. 
        /// </summary>
        /// <param name="type">
        /// The restriction type.
        /// </param>
        /// <param name="value">
        /// The value of the restriction type.
        /// </param>
        public Restriction(string type, string value)
        {
            RestrictionType = (RestrictionType)Enum.Parse(typeof(RestrictionType), type, true);
            Value = value;
        }

        /// <summary>
        /// Gets or sets RestrictionType.
        /// </summary>
        /// <value>
        /// The retriction type.
        /// </value>        
        [AttributeProperty(Name = "type")]
        public RestrictionType RestrictionType
        {
            get; 
            set;
        }

        /// <summary>
        /// Gets or sets Value.
        /// </summary>
        /// <value>
        /// The value.
        /// </value>
        [AttributeProperty(Name = "value")]
        public string Value
        {
            get; 
            set;
        }

        private int ValueToInt
        {
            get
            {
                return Convert.ToInt32(Value, CultureInfo.InvariantCulture);
            }
        }

        private decimal ValueToDecimal
        {
            get
            {
                return Convert.ToDecimal(Value, CultureInfo.InvariantCulture);
            }
        }

        /// <summary>
        /// Gets information whether the constraint was met.
        /// </summary>
        /// <param name="item">
        /// The item on which the constraint is checked.
        /// </param>
        /// <returns>
        /// was the constraint met
        /// </returns>
        public override bool IsValid(OffspringItem item)
        {
            var leafItem = item as LeafItem;
            if (leafItem == null)
            {
                throw new InvalidOffspringTypeException(OffspringType.Leaf);
            }

            try
            {
                return IsValidRestrictionType(leafItem.Value);
            }
            catch (FormatException)
            {
                return false;
            }
            catch (OverflowException)
            {
                return false;
            }
            catch (InvalidCastException)
            {
                return false;
            }            
        }        

        /// <summary>
        /// Adds max length to text box.
        /// </summary>
        /// <param name="e">
        /// The event args.
        /// </param>
        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);
            if (RestrictionType == RestrictionType.MaxLength || RestrictionType == RestrictionType.Length)
            {
                var textBoxHandler = (Owner as LeafItem).ValueHandler as TextBoxValueHandler;
                if (textBoxHandler != null)
                {
                    textBoxHandler.TextBox.MaxLength = ValueToInt;
                }
            }
        }

        /// <summary>
        /// checks if the pattern is matched
        /// </summary>
        /// <param name="pattern">
        /// The pattern.
        /// </param>
        /// <param name="text">
        /// The text to matched.
        /// </param>
        /// <returns>
        /// is the pattern matched
        /// </returns>
        private static bool MatchPattern(string pattern, string text)
        {
            var regex = new Regex(pattern);
            return regex.IsMatch(text);
        }

        /// <summary>
        /// Checks the validity of fraction digits number
        /// </summary>
        /// <param name="value">
        /// The value.
        /// </param>
        /// <param name="numberOfDigits">
        /// The provided number of digits.
        /// </param>
        /// <returns>
        /// is the number of fraction digits less or equals the provided number of digits
        /// </returns>
        private static bool MatchFractionDigits(string value, int numberOfDigits)
        {
            if (value.IndexOf(".", StringComparison.Ordinal) == -1)
            {
                return true;
            }

            return numbersRegex.Matches(value.Substring(value.LastIndexOf(".", StringComparison.Ordinal))).Count <= numberOfDigits;
        }

        /// <summary>
        /// Checks the validity of total digits number
        /// </summary>
        /// <param name="value">
        /// The value.
        /// </param>
        /// <param name="numberOfDigits">
        /// The provided number of digits.
        /// </param>
        /// <returns>
        /// is the total number of digits less or equals the provided number of digits
        /// </returns>
        private static bool MatchTotalDigits(string value, int numberOfDigits)
        {
            return numbersRegex.Matches(value).Count <= numberOfDigits;
        }

        private bool IsValidRestrictionType(string value)
        {
            if (value == null)
            {
                return false;
            }

            switch (RestrictionType)
            {
                case RestrictionType.Length:
                    return value.Length == ValueToDecimal;
                case RestrictionType.MaxLength:
                    return value.Length <= ValueToDecimal;
                case RestrictionType.MinLength:
                    return value.Length >= ValueToDecimal;
                case RestrictionType.MaxExclusive:
                    return Convert.ToDecimal(value, CultureInfo.InvariantCulture) < ValueToDecimal;
                case RestrictionType.MinExclusive:
                    return Convert.ToDecimal(value, CultureInfo.InvariantCulture) > ValueToDecimal;
                case RestrictionType.MaxInclusive:
                    return Convert.ToDecimal(value, CultureInfo.InvariantCulture) <= ValueToDecimal;
                case RestrictionType.MinInclusive:
                    return Convert.ToDecimal(value, CultureInfo.InvariantCulture) >= ValueToDecimal;
                case RestrictionType.Pattern:
                    return MatchPattern(Value, value);
                case RestrictionType.FractionDigits:
                    return MatchFractionDigits(value, ValueToInt);
                case RestrictionType.TotalDigits:
                    return MatchTotalDigits(value, ValueToInt);
                default:
                    return true;
            }
        }
    }
}